﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Windows.Forms;
using LeChatApp.ChatServer;
using Message = LeChatApp.ChatServer.Message;

namespace LeChatApp
{
    public partial class ChatClient : Form
    {
        public static User Me { get; set; }
        public static string ServerIPaddress { get; set; }
        public static ChatServer.ChatServer Server { get; set; }
        public static ChatServer.ChatServer OldServer { get; set; }
        public static bool Connected { get; set; }
        public static bool IsSecure { get; set; }
        public static List<User> ActiveUsers { get; set; }

        private static int _port;
        private static int _portSsl;

        private TcpClient _client;
        private SslStream _sslStream;

        public ChatClient()
        {
            InitializeComponent();
            ActiveUsers = new List<User>();
        }

        /***********************
         * Networking
         ***********************/
        
        private void RefreshUsers()
        {
            try
            {
                if (Connected)
                {
                    List<User> users = Server.GetUsers().ToList();
                    UpdateListUsers(users);
                }
            }
            catch (Exception)
            {
                // do nothing
            }
        }

        private void RefreshMessages()
        {
            if (Connected)
            {
                try
                {
                    Message[] messages = Server.RetreiveMessages(Me);

                    foreach (Message message in messages)
                    {
                        User fromUser = ActiveUsers.FirstOrDefault(x => x.UserID.Equals(message.From));
                        string from = fromUser == null ? "Server" : fromUser.Name;
                        UpdateChatOutput(String.Format("From: {0} >> {1}{2}", from, message.Text, Environment.NewLine));
                    }
                }
                catch (Exception)
                {
                    //do nothing
                }
                
            }
        }

        private void TmrRefreshUsersTick(object sender, EventArgs e)
        {
            // Create new thread for polling users.
            Thread threadUsers = new Thread(RefreshUsers);
            threadUsers.Start();
        }

        private void TmrRefreshMessagesTick(object sender, EventArgs e)
        {
            // Create new thread for polling messages.
            Thread threadMessages = new Thread(RefreshMessages);
            threadMessages.Start();
        }

        private void BtnMenuConnectClick(object sender, EventArgs e)
        {
            if (!Connected)
            {
                using (Connect connectDialog = new Connect())
                {
                    connectDialog.ShowDialog();

                    if (connectDialog.isSubmitted)
                    {
                        ServerIPaddress = connectDialog.cbxServerIP.Text;

                        int.TryParse(connectDialog.txt_Port.Text, out _port);
                        int.TryParse(connectDialog.txt_PortSsl.Text, out _portSsl);

                        if (connectDialog.chkSecureConnect.Checked)
                        {
                            Connect(ServerIPaddress, _portSsl, true, true, connectDialog.txtUsername.Text);
                        }
                        else
                        {
                            Connect(ServerIPaddress, _port, false, true, connectDialog.txtUsername.Text);
                        }
                        if (Connected)
                        {
                            string connInfo = connectDialog.chkSecureConnect.Checked ? "(Secure)" : "(Unsecure)";
                            UpdateChatOutput(String.Format("Connection established succesfully {0}. {1}", connInfo, Environment.NewLine));
                        }
                    }
                }
            }
            else
            {
                DisConnect();
                UpdateChatOutput("You have been disconnected." + Environment.NewLine);
            }
        }
        
        private void TxtChatInputKeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
                BtnSubmitMessageClick(sender, e);
        }

        private void BtnSubmitMessageClick(object sender, EventArgs e)
        {
            string messageText = txtChatInput.Text;
            if (!String.IsNullOrEmpty(messageText))
            {
                int selectedUserToChat = lstUsers.SelectedIndex;

                if (selectedUserToChat >= 0 && selectedUserToChat < int.MaxValue)
                {
                    Message messageToSend = new Message
                    {
                        From = Me.UserID,
                        To = ActiveUsers[selectedUserToChat].UserID,
                        Text = messageText,
                        Send = DateTime.Now
                    };

                    Server.PublishMessage(messageToSend);
                    txtChatInput.Text = string.Empty;
                    UpdateChatOutput(String.Format("To: {0} >> {1}{2}", ActiveUsers[selectedUserToChat].Name, messageText, Environment.NewLine));
                }

                else
                {
                    UpdateChatOutput("You must select a recipient before sending a message." + Environment.NewLine);
                }
            }
        }

        private void BtnMenuConnectionSecurityClick(object sender, EventArgs e)
        {
            OldServer = Server;
            int port;

            if (IsSecure)
            {
                //make sure port is specified
                if (_port == 0)
                {
                    using (SelectPort porttDialog = new SelectPort())
                    {
                        porttDialog.ShowDialog();
                        if (porttDialog.isSubmitted)
                            int.TryParse(porttDialog.txt_Port.Text, out _port);
                    }
                }
                port = _port;

                //SSL connectie opruimen
                _sslStream.Close();
                _client.Close();
            }
            else
            {
                //make sure port is specified
                if(_portSsl == 0)
                {
                    using (SelectPort porttDialog = new SelectPort())
                    {
                        porttDialog.ShowDialog();
                        if (porttDialog.isSubmitted)
                            int.TryParse(porttDialog.txt_Port.Text, out _portSsl);
                    }
                }
                port = _portSsl;
            }
            //server opruimen en naar andere verbinding schakelen
            Server.Dispose();
            Connect(ServerIPaddress, port, !IsSecure, false, null);
        }

        private void BtnMenuClearMessagesClick(object sender, EventArgs e)
        {
            txtChatOutput.Text = string.Empty;
        }

        private void Connect(string ip, int port, bool secure, bool newUser, string userName)
        {
            try
            {
                if (secure)
                {
                    Server = new ChatServer.ChatServer(string.Format("https://{0}:{1}/ChatServices.asmx", ip, port.ToString()))
                    {
                        Timeout = 30000
                    };

                    _client = new TcpClient(ip, port);
                    _sslStream = new SslStream(_client.GetStream(), false, ValidateServerCertificate, null);
                    _sslStream.AuthenticateAsClient(ServerIPaddress);
                    btnMenuConnectionSecurity.Text = "Secure (click to toggle)";
                    btnMenuConnectionSecurity.Image = Properties.Resources.lock_open;
                }
                else
                {
                    Server = new ChatServer.ChatServer(string.Format("http://{0}:{1}/ChatServices.asmx", ip, port.ToString()))
                    {
                        Timeout = 30000
                    };

                    btnMenuConnectionSecurity.Text = "Unsecure (click to toggle)";
                    btnMenuConnectionSecurity.Image = Properties.Resources._lock;
                }

                if (newUser)
                {
                    btnMenuConnectionSecurity.Enabled = true;
                    Me = Server.AddUserByName(userName);
                    btnMenuConnect.Image = Properties.Resources.disconnect;
                    btnMenuConnect.Text = " Disconnect";
                    btnSubmitMessage.Enabled = true;
                }
                else
                {
                    Message msg = new Message()
                    {
                        To = Me.UserID, 
                        Text = secure ? "Your connection is now secure." : "Your connection is now unsecure.",
                        Send = DateTime.Now
                    };
                    Server.PublishMessage(msg);
                }

                IsSecure = secure;
                Connected = true;

                string statusText = IsSecure ? "secure" : "unsecure";
                Text = String.Format("ChatClient ({0}) - Made by IT students from INHolland, The Netherlands - For educational purposes only", statusText);
            }
            //timeout afvangen
            catch (WebException)
            {
                DisConnect();
                UpdateChatOutput("Connection failed, connection timed out." + Environment.NewLine);
            }
            //verkeerde connectie info afvangen.
            catch (Exception)
            {
                DisConnect();
                UpdateChatOutput("Connection failed, incorrect connection info." + Environment.NewLine);
            }
        }

        private void DisConnect()
        {
            try
            {
                Server.RemoveUserByID(Me.UserID);
            }
            catch(WebException)
            {
                OldServer.RemoveUserByID(Me.UserID);
            }
            catch(NullReferenceException)
            {
                //do nothing
            }

            if (IsSecure)
            {
                _sslStream.Close();
                _client.Close();
            }

            //cleanup
            
            if(Server != null) Server.Dispose();
            ActiveUsers.Clear();
            lstUsers.Items.Clear();
            Connected = false;
            btnMenuConnectionSecurity.Enabled = false;
            _port = 0;
            _portSsl = 0;
            btnMenuConnect.Image = Properties.Resources.server_connect;
            btnMenuConnect.Text = "Connect";
            btnSubmitMessage.Enabled = false;

            Text = String.Format("ChatClient - Made by IT students from INHolland, The Netherlands - For educational purposes only");
        }

        //ValidateServerCertificate delegate -> cert is altijd trusted
        public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            ServicePointManager.ServerCertificateValidationCallback = (obj, c, ch, errors) => true;
            return true;
        }

        //threadsafe message aan textbox toevoegen
        delegate void UpdateChatOutputDelegate(string textToAdd);
        private void UpdateChatOutput(string textToAdd)
        {
            if (txtChatOutput.InvokeRequired)
            {
                // this is worker thread
                UpdateChatOutputDelegate del = (UpdateChatOutput);
                txtChatOutput.Invoke(del, new object[] { textToAdd });
            }
            else
            {
                // this is UI thread
                string timeStamp = DateTime.Now.ToShortTimeString();
                txtChatOutput.AppendText(timeStamp + " " + textToAdd);
            }
        }

        //threadsafe user aan lijst toevoegen
        delegate void UpdateListUsersDelegate(List<User> users);
        private void UpdateListUsers(List<User> users)
        {
            try
            {
                if (lstUsers.InvokeRequired)
                {
                    // this is worker thread
                    UpdateListUsersDelegate del = (UpdateListUsers);
                    lstUsers.Invoke(del, new object[] { users });
                }
                else
                {
                    // this is UI thread

                    // voor elke user die niet bij de client bekend is maar wel bij de server -> toevoegen aan lokale lijst
                    foreach (User userToAdd in users.Where(user => !ActiveUsers.Any(x => x.UserID.Equals(user.UserID))))
                    {
                        ActiveUsers.Add(userToAdd);
                        lstUsers.Items.Add(userToAdd.Name);
                    }

                    // voor elke user die wel bij de client bekend is maar niet bij de server -> verwijderen van lokale lijst
                    foreach (User userToRemove in ActiveUsers.Where(user => !users.Any(x => x.UserID.Equals(user.UserID))).ToList())
                    {
                        lstUsers.Items.RemoveAt(ActiveUsers.IndexOf(userToRemove));
                        ActiveUsers.Remove(userToRemove);
                    }
                }
            }
            catch (InvalidOperationException)
            {
                // niet refreshen tijdens shutdown
            }
        }
        
        //users en de aan hun gerichte berichten van de server verwijderen als ze de app sluiten
        private void ChatClientFormClosing(object sender, FormClosingEventArgs e)
        {
            if (Connected)
            {
                Server.RetreiveMessagesByUserID(Me.UserID);
                Server.RemoveUserByID(Me.UserID);
                DisConnect();
            }
        }
    }
}
